<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <title>Boost 文档本地搜索引擎</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
        /* 全局重置 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        html, body {
            height: 100%;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
            background: radial-gradient(circle at top, #1e293b 0%, #020617 55%, #000 100%);
            color: #e5e7eb;
        }

        body {
            display: flex;
            justify-content: center;
            align-items: flex-start;
            padding: 40px 12px;
        }

        .page {
            width: 100%;
            max-width: 960px;
            background: rgba(15, 23, 42, 0.92);
            border-radius: 18px;
            padding: 24px 24px 32px;
            box-shadow:
                0 24px 60px rgba(15, 23, 42, 0.8),
                0 0 0 1px rgba(148, 163, 184, 0.18);
            backdrop-filter: blur(16px);
        }

        /* 顶部标题区 */
        .header {
            display: flex;
            align-items: center;
            gap: 14px;
            margin-bottom: 20px;
        }

        .logo-dot {
            width: 28px;
            height: 28px;
            border-radius: 999px;
            background: conic-gradient(from 180deg, #0ea5e9, #22c55e, #a855f7, #0ea5e9);
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 0 18px rgba(34, 211, 238, 0.75);
        }

        .logo-dot-inner {
            width: 16px;
            height: 16px;
            border-radius: 999px;
            background: #020617;
        }

        .title-wrap {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }

        .title-wrap h1 {
            font-size: 20px;
            font-weight: 600;
            letter-spacing: 0.03em;
            color: #e5e7eb;
        }

        .title-wrap span {
            font-size: 12px;
            color: #9ca3af;
        }

        /* 搜索框整体 */
        .search-card {
            margin-top: 4px;
            margin-bottom: 18px;
            padding: 14px 16px 16px;
            border-radius: 16px;
            background: radial-gradient(circle at top left, #0f172a 0%, #020617 85%);
            border: 1px solid rgba(148, 163, 184, 0.35);
            box-shadow:
                0 18px 32px rgba(15, 23, 42, 0.85),
                0 0 0 1px rgba(15, 23, 42, 0.9);
        }

        .search-label {
            font-size: 12px;
            color: #9ca3af;
            margin-bottom: 8px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .search-label span:last-child {
            opacity: 0.8;
            font-size: 11px;
        }

        /* 搜索栏 */
        .search-row {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .search-input-wrap {
            position: relative;
            flex: 1;
        }

        .search-input {
            width: 100%;
            padding: 12px 40px 12px 14px;
            border-radius: 999px;
            border: 1px solid rgba(148, 163, 184, 0.6);
            background: rgba(15, 23, 42, 0.9);
            color: #e5e7eb;
            font-size: 14px;
            outline: none;
            transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
        }

        .search-input::placeholder {
            color: #6b7280;
        }

        .search-input:focus {
            border-color: #38bdf8;
            box-shadow:
                0 0 0 1px rgba(56, 189, 248, 0.5),
                0 0 18px rgba(56, 189, 248, 0.4);
            background: rgba(15, 23, 42, 1);
        }

        .search-icon {
            position: absolute;
            right: 12px;
            top: 50%;
            transform: translateY(-50%);
            font-size: 16px;
            color: #6b7280;
            pointer-events: none;
        }

        .search-btn {
            padding: 10px 18px;
            border-radius: 999px;
            border: none;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            color: #0b1120;
            background: linear-gradient(135deg, #38bdf8, #22c55e);
            box-shadow:
                0 10px 30px rgba(56, 189, 248, 0.45),
                0 0 0 1px rgba(15, 23, 42, 0.9);
            transition: transform 0.08s ease-out, box-shadow 0.08s ease-out, filter 0.08s ease-out;
            white-space: nowrap;
        }

        .search-btn:hover {
            transform: translateY(-1px);
            filter: brightness(1.04);
            box-shadow:
                0 16px 36px rgba(56, 189, 248, 0.55),
                0 0 0 1px rgba(15, 23, 42, 1);
        }

        .search-btn:active {
            transform: translateY(0);
            box-shadow:
                0 8px 18px rgba(56, 189, 248, 0.4),
                0 0 0 1px rgba(15, 23, 42, 1);
        }

        /* 状态信息 */
        .status-line {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 6px;
            font-size: 12px;
            color: #9ca3af;
            min-height: 18px;
        }

        .status-left {
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .status-dot {
            width: 8px;
            height: 8px;
            border-radius: 999px;
            background: #10b981;
            box-shadow: 0 0 10px rgba(16, 185, 129, 0.9);
        }

        .status-dot.offline {
            background: #f97316;
            box-shadow: 0 0 10px rgba(248, 113, 113, 0.9);
        }

        .status-right {
            opacity: 0.8;
        }

        /* 结果区域 */
        .result-wrap {
            margin-top: 10px;
        }

        .empty-tip {
            font-size: 13px;
            color: #9ca3af;
            margin-top: 10px;
        }

        .results-grid {
            margin-top: 8px;
            display: flex;
            flex-direction: column;
            gap: 12px;
        }

        .result-item {
            padding: 12px 14px;
            border-radius: 14px;
            background: radial-gradient(circle at top left, #020617 0%, #020617 45%, #030712 100%);
            border: 1px solid rgba(31, 41, 55, 0.9);
            transition: transform 0.08s ease, box-shadow 0.08s ease, border-color 0.08s ease, background 0.1s ease;
        }

        .result-item:hover {
            transform: translateY(-1px);
            border-color: rgba(56, 189, 248, 0.5);
            box-shadow:
                0 12px 26px rgba(15, 23, 42, 0.9),
                0 0 0 1px rgba(15, 23, 42, 1);
            background: radial-gradient(circle at top left, #020617 0%, #020617 40%, #020617 100%);
        }

        .result-title {
            font-size: 15px;
            font-weight: 500;
            margin-bottom: 4px;
        }

        .result-title a {
            color: #38bdf8;
            text-decoration: none;
        }

        .result-title a:hover {
            text-decoration: underline;
        }

        .result-desc {
            font-size: 13px;
            line-height: 1.55;
            color: #d1d5db;
            margin-bottom: 4px;
        }

        .result-url {
            font-size: 11px;
            color: #6ee7b7;
            word-break: break-all;
        }

        /* 高亮关键字 */
        .highlight {
            background: rgba(56, 189, 248, 0.16);
            border-radius: 4px;
            padding: 0 3px;
        }

        /* loading 小点点动画 */
        .loading-dots {
            display: inline-flex;
            align-items: center;
            gap: 3px;
        }
        .loading-dots span {
            width: 4px;
            height: 4px;
            border-radius: 999px;
            background: #38bdf8;
            animation: bounce 0.9s infinite alternate;
        }
        .loading-dots span:nth-child(2) { animation-delay: 0.18s; }
        .loading-dots span:nth-child(3) { animation-delay: 0.36s; }

        @keyframes bounce {
            0% { transform: translateY(0); opacity: 0.7; }
            100% { transform: translateY(-3px); opacity: 1; }
        }

        /* 底部 */
        .footer {
            margin-top: 18px;
            font-size: 11px;
            color: #6b7280;
            display: flex;
            justify-content: space-between;
            gap: 8px;
            flex-wrap: wrap;
        }

        @media (max-width: 640px) {
            body {
                padding: 16px 8px;
            }
            .page {
                padding: 16px 14px 20px;
            }
            .search-row {
                flex-direction: column;
                align-items: stretch;
            }
            .search-btn {
                width: 100%;
                text-align: center;
                justify-content: center;
            }
        }
    </style>
</head>
<body>
<div class="page">
    <header class="header">
        <div class="logo-dot">
            <div class="logo-dot-inner"></div>
        </div>
        <div class="title-wrap">
            <h1>Boost 文档本地搜索</h1>
            <span>基于 C++ 正/倒排索引 &amp; 本地 HTTP 服务 · 端口 8081</span>
        </div>
    </header>

    <section class="search-card">
        <div class="search-label">
            <span>查询关键字</span>
            <span>回车 或 点击「搜索」开始检索</span>
        </div>
        <form id="search-form" class="search-row">
            <div class="search-input-wrap">
                <input
                    id="search-input"
                    class="search-input"
                    type="text"
                    autocomplete="off"
                    placeholder="例如：asio、filesystem、shared_ptr、thread..."
                />
                <span class="search-icon">⌕</span>
            </div>
            <button type="submit" class="search-btn">搜索</button>
        </form>
        <div class="status-line">
            <div class="status-left">
                <span id="status-dot" class="status-dot"></span>
                <span id="status-text">等待查询...</span>
            </div>
            <div class="status-right" id="status-extra"></div>
        </div>
    </section>

    <section class="result-wrap">
        <div id="empty-tip" class="empty-tip">
            小提示：这是本地 Boost 文档索引，不需要外网。先随便搜一个试试。
        </div>
        <div id="results" class="results-grid"></div>
    </section>

    <footer class="footer">
        <span>后端：cpp-httplib (C++ HTTP library) + 自实现 Searcher</span>
        <span>前端：纯原生 HTML / CSS / JavaScript · 无外部依赖</span>
    </footer>
</div>

<script>
    const searchForm   = document.getElementById("search-form");
    const searchInput  = document.getElementById("search-input");
    const resultsEl    = document.getElementById("results");
    const emptyTipEl   = document.getElementById("empty-tip");
    const statusDot    = document.getElementById("status-dot");
    const statusText   = document.getElementById("status-text");
    const statusExtra  = document.getElementById("status-extra");

    let lastQuery = "";

    function setStatus({ text, extra = "", loading = false, error = false }) {
        statusText.textContent = text;
        statusExtra.innerHTML = extra;

        if (loading) {
            statusDot.classList.remove("offline");
            statusDot.style.background = "#38bdf8";
            statusDot.style.boxShadow = "0 0 10px rgba(56, 189, 248, 0.9)";
            statusExtra.innerHTML =
                '<span class="loading-dots"><span></span><span></span><span></span></span>';
        } else if (error) {
            statusDot.classList.add("offline");
            statusDot.style.background = "#f97316";
            statusDot.style.boxShadow = "0 0 10px rgba(248, 113, 113, 0.9)";
        } else {
            statusDot.classList.remove("offline");
            statusDot.style.background = "#10b981";
            statusDot.style.boxShadow = "0 0 10px rgba(16, 185, 129, 0.9)";
        }
    }

    function highlight(text, keyword) {
        if (!text || !keyword) return text || "";
        const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        const regex   = new RegExp(escaped, "gi");
        return text.replace(regex, m => `<span class="highlight">${m}</span>`);
    }

    function renderResults(data, query) {
        resultsEl.innerHTML = "";

        if (!Array.isArray(data) || data.length === 0) {
            emptyTipEl.textContent = "没有找到相关结果，可以换个关键词试试。";
            return;
        }

        emptyTipEl.textContent = "";

        data.forEach(item => {
            const title = item.title || "(无标题)";
            const desc  = item.desc || "";
            const url   = item.url  || "#";

            const card = document.createElement("article");
            card.className = "result-item";

            const titleDiv = document.createElement("div");
            titleDiv.className = "result-title";

            const link = document.createElement("a");
            link.href = url;
            link.target = "_blank";
            link.rel = "noopener noreferrer";
            link.innerHTML = highlight(title, query);
            titleDiv.appendChild(link);

            const descP = document.createElement("p");
            descP.className = "result-desc";
            descP.innerHTML = highlight(desc, query);

            const urlP = document.createElement("p");
            urlP.className = "result-url";
            urlP.textContent = url;

            card.appendChild(titleDiv);
            if (desc) card.appendChild(descP);
            card.appendChild(urlP);

            resultsEl.appendChild(card);
        });
    }

    async function doSearch(query) {
        if (!query || !query.trim()) {
            setStatus({ text: "请输入关键字后再搜索。", error: true });
            return;
        }

        lastQuery = query.trim();
        setStatus({
            text: `正在搜索：“${lastQuery}”`,
            loading: true
        });

        try {
            const resp = await fetch("/s?word=" + encodeURIComponent(lastQuery));
            if (!resp.ok) {
                throw new Error("HTTP " + resp.status);
            }

            const data = await resp.json();
            renderResults(data, lastQuery);

            const count = Array.isArray(data) ? data.length : 0;
            setStatus({
                text: `搜索完成，找到 ${count} 条结果。`,
                extra: "",
                loading: false,
                error: false
            });
        } catch (err) {
            console.error(err);
            resultsEl.innerHTML = "";
            emptyTipEl.textContent = "请求失败，确认后端服务是否已启动 (端口 8081)。";

            setStatus({
                text: "搜索失败，可能是后端未启动或网络错误。",
                error: true
            });
        }
    }

    searchForm.addEventListener("submit", function (e) {
        e.preventDefault();
        const q = searchInput.value;
        doSearch(q);
    });

    // 输入框回车也会触发表单提交，按 Esc 清空
    searchInput.addEventListener("keydown", function (e) {
        if (e.key === "Escape") {
            searchInput.value = "";
        }
    });

    // 初始状态
    setStatus({ text: "等待查询...", extra: "后端已绑定 /s?word=xxx 接口即可使用。" });
</script>
</body>
</html>